// Copyright (c) 2011 Blue Onion Software, All rights reserved
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Xml.Serialization;

namespace BlueOnionSoftware.Bloget
{
    public class Category : IEquatable<Category>
    {
        private string _name;

        public Category()
        {
        }

        internal Category(Category category)
        {
            if (category == null)
            {
                throw new ArgumentNullException("category");
            }
            Id = category.Id;
            _name = category._name;
            Count = category.Count;
        }

        [XmlElement("id")]
        public int Id { get; set; }

        [XmlElement("name")]
        public string Name
        {
            get { return _name ?? string.Empty; }
            set { _name = Normalize(value); }
        }

        [XmlElement("deleted")]
        public bool Deleted { get; set; }

        [XmlIgnore]
        internal int Count { get; set; }

        internal static string Normalize(string str)
        {
            Throw.IfNullOrEmpty(str, "str");
            var norm = str.Trim();
            return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(norm);
        }

        public override string ToString()
        {
            return Name ?? "name is null";
        }

        public bool Equals(Category other)
        {
            if (other == null)
            {
                return false;
            }
            return (string.Compare(Name, other.Name, StringComparison.CurrentCultureIgnoreCase) == 0);
        }
    }

    public class CategoryCollection : KeyedCollection<int, Category>
    {
        private SortedList<string, Category> _sortedCategories;
        private readonly object _syncRoot = new object();

        public CategoryCollection()
        {
        }

        internal CategoryCollection(IEnumerable<Category> categoryCollection)
        {
            Throw.IfNull(categoryCollection, "categoryCollection");

            foreach (var category in categoryCollection)
            {
                Add(new Category(category));
            }
        }

        protected override int GetKeyForItem(Category item)
        {
            Throw.IfNull(item, "item");
            return item.Id;
        }

        /// <summary>
        /// Public callers should use CreateCategory
        /// </summary>
        /// <param name="item">The object to be added to the end of the <see cref="T:System.Collections.ObjectModel.Collection`1"></see>. The value can be null for reference types.</param>
        protected new void Add(Category item)
        {
            Throw.IfNull(item, "item");
            base.Add(item);
        }

        internal Category CreateCategory(string name)
        {
            Throw.IfNullOrEmpty(name, "name");
            name = Category.Normalize(name);

            foreach (var category in this)
            {
                if (category.Name == name)
                {
                    category.Deleted = false;
                    return category;
                }
            }

            var newCategory = new Category { Name = name, Id = Count };
            Add(newCategory);
            return newCategory;
        }

        internal IEnumerable<Category> Sorted()
        {
            lock (_syncRoot)
            {
                if (_sortedCategories == null)
                {
                    _sortedCategories = new SortedList<string, Category>();
                    foreach (var category in this)
                    {
                        _sortedCategories.Add(category.Name, category);
                    }
                }
            }

            return from kvp in _sortedCategories
                   where kvp.Value.Deleted == false
                   select kvp.Value;
        }

        internal void ClearCounts()
        {
            foreach (var category in this)
            {
                category.Count = 0;
            }
        }

        internal Category GetCategoryByName(string name)
        {
            Throw.IfNullOrEmpty(name, "name");
            return this.FirstOrDefault(category => category.Name == name);
        }

        internal void Merge(Blog blog, Category to, Category from)
        {
            Throw.IfNull(to, "to");
            Throw.IfNull(from, "from");

            foreach (var post in blog.Posts)
            {
                var tags = post.Tags;
                var merge = new List<int>();

                foreach (var tag in tags)
                {
                    var newTag = (tag == from.Id) ? to.Id : tag;

                    if (merge.Contains(newTag) == false)
                    {
                        merge.Add(newTag);
                    }
                }

                post.Tags = merge.ToArray();
            }

            Remove(from);
        }
    }
}